]> git.r.bdr.sh - rbdr/map/blame - Map/Presentation/Base Components/MapTextEditor.swift
Update license URL
[rbdr/map] / Map / Presentation / Base Components / MapTextEditor.swift
CommitLineData
5e8ff485
RBR
1import Cocoa
2import SwiftUI
3
4class MapTextEditorController: NSViewController {
5
e2c37ac1 6 @Binding var document: MapDocument
fdb4633d 7 let onChange: () -> Void
5e8ff485 8
77d0155b
RBR
9 private let vertexRegex = MapParsingPatterns.vertex
10 private let edgeRegex = MapParsingPatterns.edge
11 private let blockerRegex = MapParsingPatterns.blocker
12 private let opportunityRegex = MapParsingPatterns.opportunity
fdb4633d 13 private let noteRegex = MapParsingPatterns.note
77d0155b 14 private let stageRegex = MapParsingPatterns.stage
e2c37ac1 15 private let groupRegex = MapParsingPatterns.group
77d0155b 16
fdb4633d 17 private let changeDebouncer: Debouncer = Debouncer(seconds: 1)
77d0155b 18
e2c37ac1
RBR
19 init(document: Binding<MapDocument>, onChange: @escaping () -> Void) {
20 self._document = document
fdb4633d 21 self.onChange = onChange
5e8ff485
RBR
22 super.init(nibName: nil, bundle: nil)
23 }
24
25 required init?(coder: NSCoder) {
26 fatalError("init(coder:) has not been implemented")
27 }
28
29 override func loadView() {
30 let scrollView = NSTextView.scrollableTextView()
31 let textView = scrollView.documentView as! NSTextView
32
33 scrollView.translatesAutoresizingMaskIntoConstraints = false
34
e2c37ac1 35 textView.backgroundColor = .ui.background
75a0e450 36 textView.allowsUndo = true
5e8ff485 37 textView.delegate = self
77d0155b 38 textView.textStorage?.delegate = self
e2c37ac1 39 textView.string = self.document.text
5e8ff485
RBR
40 textView.isEditable = true
41 textView.font = .monospacedSystemFont(ofSize: 16.0, weight: .regular)
42 self.view = scrollView
43 }
44
45 override func viewDidAppear() {
46 self.view.window?.makeFirstResponder(self.view)
47 }
48}
49
50extension MapTextEditorController: NSTextViewDelegate {
51
52 func textDidChange(_ obj: Notification) {
53 if let textField = obj.object as? NSTextView {
e2c37ac1
RBR
54 self.document.text = textField.string
55
56 changeDebouncer.debounce {
57 DispatchQueue.main.async {
58 self.onChange()
fdb4633d 59 }
e2c37ac1 60 }
5e8ff485
RBR
61 }
62 }
63
64 func textView(_ view: NSTextView, shouldChangeTextIn: NSRange, replacementString: String?) -> Bool
65 {
66 let range = Range(shouldChangeTextIn, in: view.string)
67 let target = view.string[range!]
68
69 if target == "--" {
70 return false
71 }
72
73 return true
74 }
75}
76
77d0155b 77extension MapTextEditorController: NSTextStorageDelegate {
fdb4633d 78
77d0155b
RBR
79 override func textStorageDidProcessEditing(_ obj: Notification) {
80 if let textStorage = obj.object as? NSTextStorage {
fdb4633d 81 self.colorizeText(textStorage: textStorage)
77d0155b
RBR
82 }
83 }
84
85 private func colorizeText(textStorage: NSTextStorage) {
86 let range = NSMakeRange(0, textStorage.length)
87 var matches = vertexRegex.matches(in: textStorage.string, options: [], range: range)
77d0155b
RBR
88
89 for match in matches {
e2c37ac1
RBR
90 textStorage.addAttributes(
91 [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1))
92 textStorage.addAttributes(
93 [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
94 textStorage.addAttributes(
95 [.foregroundColor: NSColor.syntax.number], range: match.range(at: 3))
96 textStorage.addAttributes(
97 [.foregroundColor: NSColor.syntax.option], range: match.range(at: 4))
77d0155b
RBR
98 }
99
100 matches = edgeRegex.matches(in: textStorage.string, options: [], range: range)
101
102 for match in matches {
e2c37ac1
RBR
103 textStorage.addAttributes(
104 [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1))
77d0155b
RBR
105 let arrowRange = match.range(at: 2)
106 textStorage.addAttributes(
fdb4633d 107 [.foregroundColor: NSColor.syntax.symbol],
77d0155b 108 range: NSMakeRange(arrowRange.lowerBound - 1, arrowRange.length + 1))
e2c37ac1
RBR
109 textStorage.addAttributes(
110 [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 3))
77d0155b
RBR
111 }
112
113 matches = opportunityRegex.matches(in: textStorage.string, options: [], range: range)
114
115 for match in matches {
e2c37ac1
RBR
116 textStorage.addAttributes(
117 [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
118 textStorage.addAttributes(
119 [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
120 textStorage.addAttributes(
121 [.foregroundColor: NSColor.syntax.symbol], range: match.range(at: 3))
122 textStorage.addAttributes(
123 [.foregroundColor: NSColor.syntax.number], range: match.range(at: 4))
77d0155b
RBR
124 }
125
126 matches = blockerRegex.matches(in: textStorage.string, options: [], range: range)
127
128 for match in matches {
e2c37ac1
RBR
129 textStorage.addAttributes(
130 [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
131 textStorage.addAttributes(
132 [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
fdb4633d 133 }
e2c37ac1 134
fdb4633d
RBR
135 matches = noteRegex.matches(in: textStorage.string, options: [], range: range)
136
137 for match in matches {
e2c37ac1
RBR
138 textStorage.addAttributes(
139 [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
140 textStorage.addAttributes(
141 [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
142 textStorage.addAttributes(
143 [.foregroundColor: NSColor.syntax.number], range: match.range(at: 3))
77d0155b
RBR
144 }
145
146 matches = stageRegex.matches(in: textStorage.string, options: [], range: range)
147
148 for match in matches {
e2c37ac1
RBR
149 textStorage.addAttributes(
150 [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
151 textStorage.addAttributes(
152 [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
153 }
154
155 matches = groupRegex.matches(in: textStorage.string, options: [], range: range)
156
157 for match in matches {
158 textStorage.addAttributes(
159 [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
160 textStorage.addAttributes(
161 [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
77d0155b
RBR
162 }
163 }
164}
165
5e8ff485
RBR
166struct MapTextEditor: NSViewControllerRepresentable {
167
e2c37ac1 168 @Binding var document: MapDocument
fdb4633d 169 var onChange: () -> Void = {}
5e8ff485
RBR
170
171 func makeNSViewController(
172 context: NSViewControllerRepresentableContext<MapTextEditor>
173 ) -> MapTextEditorController {
e2c37ac1 174 return MapTextEditorController(document: $document, onChange: onChange)
5e8ff485
RBR
175 }
176
177 func updateNSViewController(
178 _ nsViewController: MapTextEditorController,
179 context: NSViewControllerRepresentableContext<MapTextEditor>
fdb4633d 180 ) {}
5e8ff485 181}